package org.codefx.libfx.control.properties;
import java.util.function.Consumer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import org.codefx.libfx.control.properties.ControlProperties;
import org.codefx.libfx.control.properties.ControlPropertyListenerHandle;
/**
* Demonstrates how to use the {@link ControlPropertyListenerHandle} and its builder.
*/
@SuppressWarnings("static-method")
public class ControlPropertyListenerDemo {
// #begin CONSTRUCTION & MAIN
/**
* Creates a new demo.
*/
private ControlPropertyListenerDemo() {
// nothing to do
}
/**
* Runs this demo.
*
* @param args
* command line arguments (will not be used)
*/
public static void main(String[] args) {
ControlPropertyListenerDemo demo = new ControlPropertyListenerDemo();
demo.simpleCase();
demo.attachAndDetach();
demo.timeNoTypeCheck();
demo.timeWithTypeCheck();
demo.castVsTypeChecking();
}
// #end CONSTRUCTION & MAIN
// #begin DEMOS
/**
* Demonstrates the simple case, in which a value processor is added for some key.
*/
private void simpleCase() {
ObservableMap<Object, Object> properties = FXCollections.observableHashMap();
// build and attach the listener
ControlProperties.<String> on(properties)
.forKey("Key")
.processValue(value -> System.out.println(" -> " + value))
.buildAttached();
// set values of the correct type for the correct key
System.out.print("Set \"Value\" for the correct key for the first time: ");
properties.put("Key", "Value");
System.out.print("Set \"Value\" for the correct key for the second time: ");
properties.put("Key", "Value");
// set values of the wrong type:
System.out.println("Set an Integer for the correct key: ... (nothing will happen)");
properties.put("Key", 5);
// set values for the wrong key
System.out.println("Set \"Value\" for another key: ... (nothing will happen)");
properties.put("OtherKey", "Value");
System.out.println();
}
/**
* Demonstrates how a listener can be attached and detached.
*/
private void attachAndDetach() {
ObservableMap<Object, Object> properties = FXCollections.observableHashMap();
// build the listener (but don't attach it yet) and assign it to a variable
ControlPropertyListenerHandle listener = ControlProperties.<String> on(properties)
.forKey("Key")
.processValue(value -> System.out.println(" -> " + value))
.buildDetached();
// set a value when the listener is not yet attached
System.out.println(
"Set \"ExistingValue\" before attaching the listener: ... (nothing will happen)");
properties.put("Key", "ExistingValue");
// now attach the listener
System.out.print("When the listener is set, \"ExistingValue\" is processed and removed: ");
listener.attach();
System.out.print("Set \"Value\": ");
properties.put("Key", "Value");
// detach the listener
listener.detach();
System.out.println("Set \"UnnoticedValue\" when the listener is detached: ... (nothing will happen)");
System.out.println();
}
/**
* Measures the time it takes to get a lot of {@link ClassCastException}.
*/
private void timeNoTypeCheck() {
ObservableMap<Object, Object> properties = FXCollections.observableHashMap();
Consumer<String> unreached = value -> {
throw new RuntimeException("Should not be executed!");
};
// build and a attach a listener which does no type check before cast
ControlProperties.<String> on(properties)
.forKey("Key")
.processValue(unreached)
.buildAttached();
// add a couple of values of the wrong type to average the time that takes
Integer valueOfWrongType = 5;
int runs = (int) 1e5;
long startTimeInNS = System.nanoTime();
for (int i = 0; i < runs; i++)
properties.put("Key", valueOfWrongType);
long endTimeInNS = System.nanoTime();
long timePerRunInNS = (endTimeInNS - startTimeInNS) / runs;
System.out.println("For unchecked casts, adding a value of the wrong type takes ~" + timePerRunInNS + " ns.");
System.out.println();
}
/**
* Demonstrates how type checking increases performance if values of an incorrect type are added frequently.
*/
private void timeWithTypeCheck() {
ObservableMap<Object, Object> properties = FXCollections.observableHashMap();
Consumer<String> unreached = value -> {
throw new RuntimeException("Should not be executed!");
};
// build and a attach a listener which does a type check before cast
ControlProperties.<String> on(properties)
.forKey("Key")
.forValueType(String.class)
.processValue(unreached)
.buildAttached();
// add a couple of values of the wrong type to average the time that takes
Integer valueOfWrongType = 5;
int runs = (int) 1e5;
long startTimeInNS = System.nanoTime();
for (int i = 0; i < runs; i++)
properties.put("Key", valueOfWrongType);
long endTimeInNS = System.nanoTime();
long timePerRunInNS = (endTimeInNS - startTimeInNS) / runs;
System.out.println("For checked casts, adding a value of the wrong type takes ~" + timePerRunInNS + " ns.");
System.out.println();
}
// #end DEMOS
/**
* TODO (nipa): I don't get it. The simple test below clearly shows that raising an exception takes about 6.000 ns.
* So why the hell does {@link #timeNoTypeCheck()} run way faster than that?!
* <p>
* Some days later: I ran this again and discovered that the time difference is now very measurable and looks
* correct. Perhaps some JVM optimization because I ran it so often?
*/
private void castVsTypeChecking() {
int runs = (int) 1e5;
Object integer = 3;
// CAST
long start = System.nanoTime();
for (int i = 0; i < runs; i++)
try {
String string = (String) integer;
System.out.println(string);
} catch (ClassCastException e) {
// do nothing
}
long end = System.nanoTime();
System.out.println("Each unchecked cast took ~" + (end - start) / runs + " ns.");
// TYPE CHECK
start = System.nanoTime();
for (int i = 0; i < runs; i++)
if (String.class.isInstance(integer)) {
String bar = (String) integer;
System.out.println(bar);
}
end = System.nanoTime();
System.out.println("Each type check took ~" + (end - start) / runs + " ns.");
}
}